/* This package contains a generic, static class that can diff two IEnumerables with 
 * user-supplied diffing functions. Visit the project page for samples and more information.
 * This package is a single, easily includable .cs file that has no dependencies on anything 
 * else. Part of Code Blocks (http://codeblocks.codeplex.com) 
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;

enum DiffType
{
	Unchanged,
	Changed,
	Removed,
	Added
}
static class Differ<T> 
	where T: class
{
	[DebuggerDisplay("DiffType={DiffType}, Old={Old}, New={New}")]
	sealed class DiffEntry
	{
		public DiffEntry(T oldObject, T newObject, DiffType diffType)
		{
			Old = oldObject;
			New = newObject;
			DiffType = diffType;
		}
		
		public T Old
		{
			get;
			private set;
		}

		public T New
		{
			get;
			private set;
		}

		public DiffType DiffType
		{
			get;
			private set;
		}
	}

	private static bool Contains<T1>(IEnumerable<T1> sequence, T1 value, Comparison<T1> comparison)
	{
		foreach (var obj in sequence)
			if (comparison(value, obj) == 0)
				return true;

		return false;
	}

	private static T1 Find<T1>(IEnumerable<T1> sequence, T1 value, Comparison<T1> comparison)
	{
		foreach (var obj in sequence)
			if (comparison(value, obj) == 0)
				return obj;
		
		return default(T1);
	}

	static IEnumerable<DiffEntry> Diff(IEnumerable<T> oldData, IEnumerable<T> newData, Comparison<T> identity, Comparison<T> different)
	{
		return (from newObj in newData
			   let oldObj = Find(oldData, newObj, identity)
			   let existsInBoth = oldObj != null
			   select existsInBoth ? new DiffEntry(oldObj, newObj, different(oldObj, newObj) == 0 ? DiffType.Unchanged : DiffType.Changed) :
					new DiffEntry(oldObj, newObj, DiffType.Added))
			.Union(from oldObj in oldData
				   where !Contains(newData, oldObj, identity)
				   select new DiffEntry(oldObj, null, DiffType.Removed));
	}
}